/*
The hdd.c file implements the low-level Hard Disk driver
*/


#include <globals.h>
#include <klib.h>
#include <interrupts.h>
#include <panic.h>

//Private Includes
//#include "include/scancodes.h"
#include <ide.h>

/* Redefine EXTERN because we'll be defining functions for real here */ 


/* Data structures */

                                /* System Memory Pages */
//dword_t nr_pages;               /* Number of memory pages above the 16 MB mark */

static int SPT=63;          /* Sectors Per Track */
static int HPC=16;          /* Heads Per Cylinder */

/* Private functions prototypes */

extern void hdd_interrupt(void);

void hdd_isr(void);
int hdd_setup(int drive,int lba,int count);
int hdd_detect(int controller_port,int drive);
void print_result(char *msg,int r);
int lba_to_chs(long lba,int *c,int *h,int *s);
void chs_to_lba(int c,int h,int s,long *lba);

/* Functions implementation */

void hdd_isr(void)
  {
    printf("HDD interrupt\n");
  }

void chs_to_lba(int c,int h,int s,long *lba)
  {
    *lba=(s-1)+(h*SPT)+(c*HPC*SPT);
  }

int lba_to_chs(long lba,int *c,int *h,int *s)
  {
    int tmp;

    tmp=lba/SPT;
    *c=tmp/HPC;
    *h=tmp%HPC;
    *s=(lba%SPT)+1;

    /*
    tmp = lba / SPT;
    *c=tmp/(HPC+1);
    *h=tmp%(HPC+1);
    *s=(lba%SPT)+1;
    */

    /*
    *c = lba / (SPT * HPC);
    tmp = lba % (SPT * HPC);
    *h = tmp / SPT;
    *s = tmp % SPT + 1;
    */


    // sector is 8 bit
    *s&=0x00FF;
    // head is 4 bit
    *h&=0x000F;

    return 0;
  }

/*
// This version uses CHS
int hdd_setup(int drive,int lba,int count)
  {
    int cylinder,head,sector;
    byte_t val;
    
    // drive not valid
    if (drive<0 || drive>1)
      return 1;

    // convert LBA address to CHS
    lba_to_chs(lba,&cylinder,&head,&sector);

    // Drive and head port
    val=(byte_t)((0x0A | drive)<<4 | head);
    outb(0x1F6,val);
    // Sector count port
    outb(0x1F2,count);
    // Sector number port 
    outb(0x1F3,(byte_t)sector);
    // Cylinder low port
    val=(byte_t)(cylinder & 0x00FF);
    outb(0x1F4,val);
    // Cylinder high port
    val=(byte_t)((cylinder>>8) & 0x00FF);
    outb(0x1F5,val);

    return 0;
  }
*/

// This version uses LBA28
int hdd_setup(int drive,int lba,int count)
  {
    // drive not valid
    if (drive<0 || drive>1)
      return 1;


    // Send a NULL Byte
    outb(0x1F1, 0x00);
    // Sector count port
    outb(0x1F2,count);
    // Low 8 bits of LBA address
    outb(0x1F3, (unsigned char)lba & 0x00FF);
    // Next 8 bits of LBA address
    outb(0x1F4, (unsigned char)(lba >> 8) & 0x00FF);
    // Final 8 bits of LBA address
    outb(0x1F5, (unsigned char)(lba >> 16) & 0x00FF);
    // Drive, magic bits, highest 4 bits of LBA
    outb(0x1F6, 0xE0 | (drive << 4) | ((lba >> 24) & 0x0F));

    return 0;
  }

int hdd_detect(int controller_port,int drive)
  {
    byte_t magic,val;

    // magic value
    magic=0x88;

    // Write a magic value to the LBA port
    outb(controller_port + 0x03, magic);
    // read back
    val=inb(controller_port + 0x03);

    // controller not present
    if (val!=magic)
      return 2;

    // if we're here, the controller is present

    // Pack drive (use head=0)
    val=(byte_t)((0x0A | drive)<<4);

    // send drive/head
    outb(controller_port + 0x06, val); 

    // wait 1/250th of a second
    //sleep(1); 
    for (val=0;val<126;val++)
      val *= 1;

    // read the status port
    val = inb(controller_port + 0x07); 

    // see if the busy bit is set
    // drive is present
    if (val & 0x40) 
      return 0;

    // drive is not present
    return 1;
  }


void print_result(char *msg,int r)
 {
   printf("Checking %s: ",msg);

   switch (r)
     {
     case 0:
       printf("drive present.\n");
       break;

     case 1:
       printf("drive not present.\n");
       break;

     case 2:
       printf("controller not present.\n");
       break;
  
     default:
       printf("unknown return code.\n");
     }
 }


/* Public Functions implementation */

void hdd_initialize(void)
  {
    int ret;

    //assign_IRQ(1,(dword_t)&hdd_interrupt);

    // Controller 0, Drive 0
    ret=hdd_detect(IDE_PRIMARY,0);
    print_result("C=0,D=0",ret);

    // Controller 0, Drive 1
    ret=hdd_detect(IDE_PRIMARY,1);
    print_result("C=0,D=1",ret);

    // Controller 1, Drive 0
    ret=hdd_detect(IDE_SECONDARY,0);
    print_result("C=1,D=0",ret);

    // Controller 1, Drive 1
    ret=hdd_detect(IDE_SECONDARY,1);
    print_result("C=1,D=1",ret);

    return;
  }

void hdd_read_sector(int drive,int lba,int count,byte_t *buffer)
  {
    int num_words;
    word_t *buf;

    // 256 is 512/2 = half a sector
    num_words=count*256;
    buf=(word_t *)buffer;

    hdd_setup(drive,lba,count);

    // Command port - read with retry
    outb(0x1F7,0x20);

    while (num_words--)
      {
	// Test sector buffer ready (busy wait)
	while (!(inb(0x1F7) & 0x08));
	// Data port 
	*buf++=inw(0x1F0);
      }
  }

void hdd_write_sector(int drive,int lba,int count,byte_t *buffer)
  {
    int num_words;
    word_t *buf;

    // 256 is 512/2 = half a sector
    num_words=count*256;
    buf=(word_t *)buffer;

    hdd_setup(drive,lba,count);

    // Command port - write with retry
    outb(0x1F7,0x30);

    while (num_words--)
      {
	// Test sector buffer ready (busy wait)
	while (!(inb(0x1F7) & 0x08));
	// Data port 
	outw(0x1F0,*buf++);
      }
  }


/*
Warning: not tested. LBA48 may not be supported by all controllers.

To read a sector using LBA48:
1.  Send two NULL bytes to port 0x1F1: outb(0x1F1, 0x00); outb(0x1F1, 0x00);
2.  Send a 16-bit sector count to port 0x1F2: outb(0x1F2, 0x00); outb(0x1F2, 0x01);
3.  Send bits 24-31 to port 0x1F3: outb(0x1F3, (unsigned char)(addr >> 24));
4.  Send bits 0-7 to port 0x1F3: outb(0x1F3, (unsigned char)addr);
5.  Send bits 32-39 to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 32));
6.  Send bits 8-15 to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 8));
7.  Send bits 40-47 to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 40));
8.  Send bits 16-23 to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 16));
9.  Send the drive indicator and some magic bits to port 0x1F6: outb(0x1F6, 0x40 | (drive << 4));
10. Send the command (0x24) to port 0x1F7: outb(0x1F7, 0x24);
  
To write a sector using LBA48:
Do all the same as above, but send 0x34 for the command byte, instead of 0x24: outb(0x1F7, 0x34);
*/
